/**
 * 说到'读取', 其实有很多种, 不仅仅是obj.foo这种叫读取
 * foo in obj, 也是读取
 * for ... in 也是读取
 */
// 1. foo in obj
const data = { foo: '1', bar: '2' }
const obj = new Proxy(data, {
  has(target, key) {
    // console.log('has')
    return Reflect.has(target, key)
  }
})

if ('foo' in obj) {
  // console.log('yes obj has foo')
}

// 2. for...in
const obj1 = new Proxy(data, {
  ownKeys(target) {
    return Reflect.ownKeys(target)
  }
})
// 使用ownKeys方法可以捕捉到for...in的读取操作
for (const key in obj1) {
  // console.log(key, 'key')
}

{
  let activeEffect
  const bucket = new WeakMap()
  let effectStack = []
  // 增加, 负责存储迭代的key
  const ITERATE_KEY = Symbol()
  function track(target, key) {
    let depsMap = bucket.get(target)
    if (!depsMap) {
      bucket.set(target, (depsMap = new Map()))
    }
    let deps = depsMap.get(key)
    if (!deps) {
      depsMap.set(key, (deps = new Set()))
    }
    if (activeEffect) {
      deps.add(activeEffect)
      activeEffect.deps.push(deps)
    }
  }

  // 触发事件
  function trigger(target, key, type) {
    const depsMap = bucket.get(target)
    if (!depsMap) return
    const effects = depsMap.get(key)
    const effectsToRun = new Set()
    effects &&
      effects.forEach((effect) => {
        if (effect !== activeEffect) {
          effectsToRun.add(effect)
        }
      })

    // 只有是新增和删除的时候, 才会检查是否存在迭代
    if (type === 'ADD' || type === 'DELETE') {
      // 如果一个key触发了, 还要看它是否有迭代
      // 增加, 迭代key存储的副作用函数
      const iterateEffects = depsMap.get(ITERATE_KEY)

      // 获取所有迭代副作用
      iterateEffects &&
        iterateEffects.forEach((effect) => {
          if (effect !== activeEffect) {
            effectsToRun.add(effect)
          }
        })
    }

    effectsToRun.forEach((effect) => {
      if (effect.options.schedule) {
        effect.options.schedule(effect)
      } else {
        effect()
      }
    })
  }

  const obj = new Proxy(data, {
    get(target, key, receiver) {
      track(target, key)
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      // 首先检查这key是否属于对象
      const type = Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
      const res = Reflect.set(target, key, value, receiver)
      trigger(target, key, type)
      return res
    },
    // 'key' in obj 操作
    has(target, key) {
      return Reflect.has(target, key)
    },
    // for (const key in obj) {} 迭代操作
    ownKeys(target) {
      console.log('ownkeys')
      // 为被迭代对象的depMaps增加一个ITERATE_KEY
      track(target, ITERATE_KEY)
      return Reflect.ownKeys(target)
    },
    // delete obj.foo操作
    deleteProperty(target, key) {
      // 是否存在该key
      const hadKey = Object.prototype.toString.call(target, key)
      // 删除属性
      const res = Reflect.deleteProperty(target, key)
      // 如果存在该键, 并且删除成功
      if (hadKey && res) {
        // 执行删除副作用函数
        trigger(target, key, 'DELETE')
      }
      // 注意返回
      return res
    }
  })

  function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
      const deps = effectFn.deps[i]
      deps.delete(effectFn)
    }
    effectFn.deps.length = 0
  }

  function effect(fn, options = {}) {
    const effectFn = () => {
      cleanup(effectFn)
      activeEffect = effectFn
      effectStack.push(effectFn)
      const res = fn()
      effectStack.pop()
      activeEffect = effectStack[effectStack.length - 1]
      return res
    }
    effectFn.deps = []
    effectFn.options = options
    if (!options.lazy) {
      effectFn()
    }
    return effectFn
  }
  effect(() => {
    for (const key in obj) {
      // 注意, 这里如果写上obj[key], 那这个副作用函数就会被
      // 加到对应的key下的set里了
      console.log(key, '迭代')
    }
  })
  setTimeout(() => {
    delete obj.foo
  }, 1000)
}

/**
 * 总结:
 * 本章针对的是对象代理的几种特殊情况
 * 1. 使用in操作符的时候, 需要借用Proxy的has方法
 * 2. 如果是for...in的时候需要注意,
 *    a. 它使用ownKeys方法拦截
 *    b. 由于迭代是针对所有的key, 所以需要定义一个ITERATE作为所有迭代副作用函数的key
 *    c. 如果新增属性, 则需要在trigger的时候, 将ITERATE中的effects取出执行
 *    d. 如果是修改, 则无需调用迭代相关的effect
 * 3. 如果是delete删除属性, 则需要在判断确实存在该属性, 且删除成功之后, 重新执行迭代effect
 */
